<?php
/***********************************************************
 * 数据库备份
 * @作者 pcfcms <1131680521@qq.com>
 * @版权 广州市春风科技有限公司
 * @主页 http://www.pcfcms.com
 * @时间 2019年12月21日
***********************************************************/
namespace app\admin\controller\system;
use app\admin\model\Backup;
use app\admin\controller\Base;
use think\facade\Request;
use think\facade\Session;
use think\facade\Db;
use think\facade\Cache;
class Tools extends Base
{
    public $popedom = '';
    public function initialize() {
        parent::initialize();
        $ctl_act = Request::controller().'/index';
        $this->popedom = appfile_popedom($ctl_act);
    }

    // 数据库列表 
    // by 小潘 2020.03.13
    public function index(){
        //验证查看权限
        if(!$this->popedom["list"]){
            return $this->errorNotice(config('params.auth_msg.list'),true,3,false);
        }
        if (Request::isAjax()) {
            if(isset($post['limit'])){
                $limit = $post['limit'];
            }else{
                $limit = 10;
            }
            $dbtables = Db::query('SHOW TABLE STATUS');
            $total = 0;
            $list = array();
            foreach ($dbtables as $k => $v) {
                $v['size'] = format_bytes($v['Data_length'] + $v['Index_length']);
                $list[$k] = $v;
            }

            $path = root_path()."backup/sqldata/";//数据库备份目录【配置设置】
            $path = !empty($path) ? $path : root_path()."backup/sqldata/";
            $path = str_replace("\/", "/", $path);
            $path = str_replace('//','/' , $path);
            @unlink($path.'backup.lock');

            //备份完成，清空缓存
            session::set('backup_tables', null);
            session::set('backup_file', null);
            session::set('backup_config', null);
            $result = ['code' => 0, 'data' => $list,'count'=>''];
            return $result;
        }
        return $this->fetch();
    }

    // 数据库优化 
    // by 小潘 2020.03.13
    public function optimize(){
        if (Request::isPost()) {
            if(!$this->popedom["optimze"]){
                if(config('params.auth_msg.test')){
                    $result = ['status' => false, 'msg' => config('params.auth_msg.pcfcms')];
                    return $result;
                }else{
                    $result = ['status' => false, 'msg' => config('params.auth_msg.optimze')];
                    return $result;                    
                }
            }
            $table[] = input('tablename' , '');
            if (empty($table)) {
                $result = ['status' => false, 'msg' => '请选择数据表！'];
                return $result;
            }
            $strTable = implode(',', $table);
            if (!DB::query("OPTIMIZE TABLE {$strTable} ")) {
                $strTable = '';
            }
            $result = ['status' => true, 'msg' => '全部优化成功','url'=>Request::baseFile().'/system.tools/index'];
            return $result;
        }
        $result = ['status' => false, 'msg' => '操作失败！'];
        return $result;
    }

    // 数据库修复 
    // by 小潘 2020.03.13
    public function repair(){
        if (Request::isPost()) {
            if(!$this->popedom["repair"]){
                if(config('params.auth_msg.test')){
                    $result = ['status' => false, 'msg' => config('params.auth_msg.pcfcms')];
                    return $result;
                }else{
                    $result = ['status' => false, 'msg' => config('params.auth_msg.repair')];
                    return $result;                    
                }
            }
            $table[] = input('tablename' , '');
            if (empty($table)) {
                $result = ['status' => false, 'msg' => '请选择数据表！'];
                return $result;
            }
            $strTable = implode(',', $table);
            if (!DB::query("REPAIR TABLE {$strTable} ")) {
                $strTable = '';
            }
            $result = ['status' => true, 'msg' => '全部修复成功','url'=>Request::baseFile().'/system.tools/index'];
            return $result;
        }
        $result = ['status' => false, 'msg' => '操作失败！'];
        return $result;
    }

    // 数据备份 
    // by 小潘 2020.03.13
    public function export($tables = null,$id = null,$start = null,$optstep = 0)
    {
        //验证修改权限
        if(!$this->popedom["export"]){
            if(config('params.auth_msg.test')){
                $result = ['status' => 0, 'info' => config('params.auth_msg.pcfcms')];
                return $result;
            }else{
                $result = ['status' => 0, 'info' => config('params.auth_msg.export')];
                return $result;                    
            }
        } 
        //防止备份数据过程超时
        function_exists('set_time_limit') && set_time_limit(0);
        //升级完自动备份所有数据表
        if ('all' == $tables){
            $dbtables = Db::query('SHOW TABLE STATUS');
            $list = array();
            foreach ($dbtables as $k => $v) {
                $list[] = $v['Name'];
            }
            $tables = $list;
            $path1 = root_path()."backup/sqldata/";//数据库备份目录【配置设置】
            $path1 = !empty($path1) ? $path1 : root_path()."backup/sqldata/";
            $path1 = str_replace("\/", "/", $path1);
            $path1 = str_replace('//','/' , $path1);
            @unlink($path1.'backup.lock');
        }
        if(Request::isPost() && !empty($tables) && is_array($tables) && empty($optstep))
        {
            //初始化
            $path = tpCache('global.web_sqldatapath');
            $path = !empty($path) ? root_path().$path : root_path()."backup/sqldata/";
            $path = str_replace("\/", "/", $path);
            $path = str_replace('//','/' , $path);
            if(!empty($path) && !is_dir($path)){
                mkdir($path, 0755, true);
            }
            //读取备份配置
            $config = array(
                'path'     => $path,
                'part'     => 52428800,
                'compress' =>  0,
                'level'    => 9,
            );
            //检查是否有正在执行的任务
            $lock = $path."/backup.lock";
            if(is_file($lock)){
                $result = ['status' => 0, 'info' => '检测到有一个备份任务正在执行，请稍后再试！'];
                return $result;
            }else{
                //创建锁文件
                file_put_contents($lock, $_SERVER['REQUEST_TIME']);
            }
            //检查备份目录是否可写
            if(!is_writeable($config['path'])){
                $result = ['status' => 0, 'info' => '备份目录不存在或不可写，请检查后重试！'];
                return $result;
            }
            //缓存要备份的备份信息
            session::set('backup_config', $config);
            //生成备份文件信息
            $file = array(
                'name' => date('Ymd-His', $_SERVER['REQUEST_TIME']),
                'part' => 1,
                'version' => getCmsVersion(),
            );
            //缓存要备份的文件
            session::set('backup_file', $file);
            //缓存要备份的表
            session::set('backup_tables', $tables);
            //创建备份文件
            $Database = new Backup($file, $config);
            if(false !== $Database->Backup_Init()){
                $speed = (floor((1/count($tables))*10000)/10000*100);
                $speed = sprintf("%.2f", $speed);
                $tab = array('id' => 0, 'start' => 0, 'speed'=>$speed, 'table'=>$tables[0], 'optstep'=>1);
                $result = ['status' => 1, 'info' => '初始化成功！','tab'=>$tab,'tables'=>$tables];
                return $result;
            }else{
                $result = ['status' => 0, 'info' => '初始化失败，备份文件创建失败！'];
                return $result;
            }
        }elseif (Request::isPost() && is_numeric($id) && is_numeric($start) && 1 == intval($optstep))
        {
            $tables = session::get('backup_tables');
            //备份指定表
            $Database = new Backup(session::get('backup_file'), session::get('backup_config'));
            $start  = $Database->backup($tables[$id], $start);
            if(false === $start){
                $result = ['status' => 0, 'info' => '备份出错！'];
                return $result;
            } elseif (0 === $start) { 
                //下一表
                if(isset($tables[++$id])){
                    $speed = (floor((($id+1)/count($tables))*10000)/10000*100);
                    $speed = sprintf("%.2f", $speed);
                    $tab = array('id' => $id, 'start' => 0, 'speed' => $speed, 'table'=>$tables[$id], 'optstep'=>1);
                    $result = ['status' => 1, 'info' => '备份完成！','tab'=>$tab];
                    return $result;
                } else { 
                    //备份完成，清空缓存
                    /*自动覆盖安装目录数据库*/
                    $install_time = time();
                    $install_path = root_path().'install';
                    if (!is_dir($install_path) || !file_exists($install_path)) {
                        $install_path = root_path().'install_'.$install_time;
                    }
                    if (is_dir($install_path) && file_exists($install_path)) {
                        $srcfile = session::get('backup_config.path').session::get('backup_file.name').'-'.session::get('backup_file.part').'-'.session::get('backup_file.version').'.sql';
                        $dstfile = $install_path.'/pcfweb.sql';
                        if(@copy($srcfile, $dstfile)){
                            //替换所有表的前缀为官方默认pcf_，并重写安装数据包里
                            $pcfDbStr = file_get_contents($dstfile);
                            $dbtables = Db::query('SHOW TABLE STATUS');
                            foreach ($dbtables as $k => $v) {
                                $tableName = $v['Name'];
                                if (preg_match('/^'.config('database.connections.mysql.prefix').'/i', $tableName)) {
                                    $pcfTableName = preg_replace('/^'.config('database.connections.mysql.prefix').'/i', 'pcf_', $tableName);
                                    $pcfDbStr = str_replace('`'.$tableName.'`', '`'.$pcfTableName.'`', $pcfDbStr);
                                }
                            }
                            @file_put_contents($dstfile, $pcfDbStr);
                        } else {
                            @unlink($dstfile);//复制失败就删掉，避免安装错误的数据包
                        }
                    }
                    @unlink(session::get('backup_config.path') . 'backup.lock');
                    session::set('backup_tables', null);
                    session::set('backup_file', null);
                    session::set('backup_config', null);
                    $result = ['status' => 1, 'info' => '备份完成！'];
                    return $result;
                }
            } else {
                $rate = floor(100 * ($start[0] / $start[1]));
                $speed = floor((($id+1)/count($tables))*10000)/10000*100 + ($rate/100);
                $speed = sprintf("%.2f", $speed);
                $tab  = array('id' => $id, 'start' => $start[0], 'speed' => $speed, 'table'=>$tables[$id], 'optstep'=>1);
                $result = ['status' => 1, 'info' => '正在备份...({$rate}%)','tab'=>$tab];
                return $result;
            }
        }
        else
        {
            $result = ['status' => 1, 'info' => '参数有误'];
            return $result;
        }
    }

    // 数据还原 
    // by 小潘 2020.03.13
    public function restore()
    {
        if(!$this->popedom["list"]){
            return $this->errorNotice(config('params.auth_msg.list'),true,3,false);
        }
        if (Request::isAjax()) {
            $path = tpCache('web.web_sqldatapath');
            $path = !empty($path) ? root_path().$path : root_path()."backup/sqldata/";
            $path = str_replace("\/", "/", $path);
            $path = str_replace('//','/' , $path);
            if(!empty($path) && !is_dir($path)){
                mkdir($path, 0755, true);
            }
            $flag = \FilesystemIterator::KEY_AS_FILENAME;
            $glob = new \FilesystemIterator($path,  $flag);
            $list = array();
            $filenum = $total = 0;
            foreach ($glob as $name => $file) {
                if(preg_match('/^\d{8,8}-\d{6,6}-\d+-v\d+\.\d+\.\d+(.*)\.sql(?:\.gz)?$/', $name)){
                    $name = sscanf($name, '%4s%2s%2s-%2s%2s%2s-%d-%s');
                    $date = "{$name[0]}-{$name[1]}-{$name[2]}";
                    $time = "{$name[3]}:{$name[4]}:{$name[5]}";
                    $part = $name[6];
                    $version = preg_replace('#\.sql(.*)#i', '', $name[7]);
                    $info = pathinfo($file);
                    if(isset($list["{$date} {$time}"])){
                        $info = $list["{$date} {$time}"];
                        $info['part'] = max($info['part'], $part);
                        $info['size'] = $info['size'] + $file->getSize();
                    } else {
                        $info['part'] = $part;
                        $info['size'] = $file->getSize();
                    }
                    $info['compress'] = ($info['extension'] === 'sql') ? '-' : $info['extension'];
                    $info['time']  = strtotime("{$date} {$time}");
                    $info['showtime']  = pcftime(strtotime("{$date} {$time}"));
                    $info['version']  = $version;
                    $filenum++;
                    $info['size'] = format_bytes($info['size']);
                    $list["{$info['time']}"] = $info;
                }
            }
            array_multisort($list, SORT_DESC);
            $result = ['code' => 0, 'data' => $list,'count'=>$filenum];
            return $result;
        }
        return $this->fetch();
    }

    // 下载 
    // by 小潘 2020.03.13
    public function downFile($time = 0)
    {
        //验证查看权限
        if(!$this->popedom["down"]){
            return $this->errorNotice(config('params.auth_msg.down'),true,3,false);
        }
        $name  = date('Ymd-His', $time) . '-*.sql*';
        $path = tpCache('global.web_sqldatapath');
        $path = !empty($path) ? root_path().$path : root_path()."backup/sqldata/";
        $path = str_replace("\/", "/", $path);
        $path = str_replace('//','/' , $path). $name;
        $files = glob($path);
        if(is_array($files)){
            foreach ($files as $filePath){
                if (!file_exists($filePath)) {
                    return $this->errorNotice('该文件不存在，可能是被删除',true,3,false);
                }else{
                    $filename = basename($filePath);
                    header("Content-type: application/octet-stream");
                    header('Content-Disposition: attachment; filename="' . $filename . '"');
                    header("Content-Length: " . filesize($filePath));
                    readfile($filePath);
                }
            }
        }
    }

    // 删除备份文件 
    // by 小潘 2020.03.13
    public function del()
    {
        if(!$this->popedom["delete"]){
            if(config('params.auth_msg.test')){
                $result = ['status' => 0, 'msg' => config('params.auth_msg.pcfcms')];
                return $result;
            }else{
                $result = ['status' => 0, 'msg' => config('params.auth_msg.delete')];
                return $result;                    
            }
        } 
        $time_arr = input('del_id/a');
        $time_arr = eyIntval($time_arr);
        if(is_array($time_arr) && !empty($time_arr)){
            foreach ($time_arr as $key => $val) {
                $name  = date('Ymd-His', $val) . '-*.sql*';
                $path = tpCache('global.web_sqldatapath');
                $path = !empty($path) ? root_path().$path : root_path()."backup/sqldata/";
                $path = str_replace("\/", "/", $path);
                $path = str_replace('//','/' , $path). $name;
                array_map("unlink", glob($path));
                if(count(glob($path))){
                    $result = ['code' => 0, 'msg' => '备份文件删除失败，请检查目录权限！'];
                    return $result;
                }
            }
            $result = ['code' => 1, 'msg' => '删除成功！'];
            return $result;
        } else {
            $result = ['code' => 0, 'msg' => '参数有误'];
            return $result;
        }
    }

    // 执行还原数据库操作 
    // by 小潘 2020.03.13
    public function new_import($time = 0)
    {
        if(!$this->popedom["import"]){
            if(config('params.auth_msg.test')){
                $result = ['status' => 0, 'msg' => config('params.auth_msg.pcfcms')];
                return $result;
            }else{
                $result = ['status' => 0, 'msg' => config('params.auth_msg.import')];
                return $result;                    
            }
        }
        function_exists('set_time_limit') && set_time_limit(0);
        $time = input('time/a');
        $time = eyIntval($time[0]);
        if(is_numeric($time) && intval($time) > 0){
            // 获取备份文件信息
            $name  = date('Ymd-His', $time) . '-*.sql*';
            $path = tpCache('global.web_sqldatapath');
            $path = !empty($path) ? root_path().$path : root_path()."backup/sqldata/";
            $path = str_replace("\/", "/", $path);
            $path = str_replace('//','/' , $path). $name;
            $files = glob($path);
            $list  = array();
            foreach($files as $name){
                $basename = basename($name);
                $match    = sscanf($basename, '%4s%2s%2s-%2s%2s%2s-%d-%s');
                $gz       = preg_match('/^\d{8,8}-\d{6,6}-\d+-v\d+\.\d+\.\d+(.*)\.sql.gz$/', $basename);
                $list[$match[6]] = array($match[6], $name, $gz);
            }
            ksort($list);
            // 检测文件正确性
            $last = end($list);
            $file_path_full = !empty($last[1]) ? $last[1] : '';
            if (file_exists($file_path_full)) {
                // 校验sql文件是否属于当前CMS版本
                preg_match('/(\d{8,8})-(\d{6,6})-(\d+)-(v\d+\.\d+\.\d+(.*))\.sql/i', $file_path_full, $matches);
                $version = getCmsVersion();
                if ($matches[4] != $version) {
                    $result = ['code' => 0, 'msg' => 'sql不兼容当前版本：'.$version];
                    return $result;
                }
                $sqls = Backup::parseSql($file_path_full);
                if(Backup::install($sqls)){
                    // 清除缓存
                    Cache::clear();//清除数据缓存文件
                    $admin_temp = glob(root_path() . 'runtime/admin/temp/'. '*.php');//清除后台临时文件缓存
                    array_map('unlink', $admin_temp);
                    $result = ['code' => 1, 'msg' => '操作成功'];
                    return $result;
                }else{
                    $result = ['code' => 0, 'msg' => '操作失败'];
                    return $result;
                }
            }
        }else{
            $result = ['code' => 0, 'msg' => '参数有误'];
            return $result;
        }
    }

}
